#!/usr/bin/env python
""" AnyBackup is a windows specific backup program to keep multiple volumes in sync with multiple volumes
    Copyright (C) 2014  Andrew Hopkinson

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    Exported Classes:

    easyListCtrl -- a wrapper for ListCtrl which transparently gives the easy of use found in ListBox but with all the
                    bells and whistles of ListCtrl
    DictView -- A simple listctrl based widget for visualizing one-dimensional dictionaries
    """

import wx
import os
import DriveUtil
from enum import AEnums
from sql import SQLLite
import datetime


class DictView(wx.ListCtrl):
    """
    A simple listctrl based widget for visualizing one-dimensional dictionaries
    """
    def __init__(self, parent, id=-1, position=wx.DefaultPosition, size=wx.DefaultSize,
                 style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES | wx.LC_VIRTUAL, hideHeaders=True):
        super(DictView, self).__init__(parent, id, position, size, style)
        self.items = {}
        self.itemList = []
        self.attrs = {}
        self.InsertColumn(0, 'Key', wx.LIST_FORMAT_LEFT)
        self.InsertColumn(1, 'Value', wx.LIST_FORMAT_LEFT, width=size.width-90)
        self.SetSingleStyle(wx.LC_NO_HEADER, hideHeaders)
        self.SetItemCount(0)

    def updateDict(self, pairs, color=None):
        """
        pairs -- an iterable of pairs (iterable of size two) that you want added to the dictionary + widget
        color -- the background color that should be set for the provided pairs
        """
        for k, v in pairs:
            new = k not in self.items
            if new:
                self.itemList.append((k, v))
                self.items[k] = self.itemList.index((k, v))
            else:
                self.itemList[self.items[k]] = (k, v)
            if color:
                self.attrs[self.items[k]] = wx.ListItemAttr()
                self.attrs[self.items[k]].SetBackgroundColour(color)
            elif self.items[k] in self.attrs:
                self.attrs.pop(self.items[k])
            self.SetItemCount(len(self.itemList))
            self.RefreshItem(self.items[k])

    def OnGetItemText(self, item, col):
        return self.itemList[item][col]

    def OnGetItemAttr(self, item):
        return self.attrs.get(item)

    def OnGetItemImage(self, item):
        return -1

    def adjust(self):
        for i in xrange(2):
            self.SetColumnWidth(i, wx.LIST_AUTOSIZE)


class easyListCtrl(object):
    """ a wrapper for ListCtrl which transparently gives the ease of use found in ListBox
        but with all the bells and whistles of ListCtrl """
    DRIVE = 'drive'
    FILE = 'file'
    STRING = 'string'

    def __init__(self, parent, wxId, fileUtil, mode=None, single=True):
        self.parent = parent
        self.mapping = {}
        self.fileUtil = fileUtil
        self.count = 0
        self.mode = mode
        self.itemDataMap = {}
        self.auto = False
        self.single = single
        self.r = SQLLite()
        if not mode:
            self.mode = self.STRING
        style = wx.LC_REPORT
        if self.single:
            style |= wx.LC_SINGLE_SEL
        self.listCtrl = wx.ListCtrl(parent, wxId, wx.DefaultPosition, wx.DefaultSize, style)
        self.setMode(self.mode)
        self.listCtrl.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag)

    # noinspection PyUnusedLocal
    def startDrag(self, evt):
        """
        Implements drag-and-drop functionality with windows, but only if the listctrl is in file mode
        """
        if self.mode != self.FILE:
            return
        data = wx.FileDataObject()
        files = self.GetSelectedData()
        if type(files) != list:
            files = [files]
        if not all([type(x) == dict for x in files]):
            return
        paths = [x for x in self.r.getFullPaths(files) if x]
        if not paths:
            return
        [data.AddFile(x) for x in paths]
        if not data.GetFilenames():
            return
        dropSource = wx.DropSource(self.listCtrl)
        dropSource.SetData(data)
        dropSource.DoDragDrop(flags=wx.Drag_CopyOnly)

    def GetSelectedData(self):
        """
        Returns the data attached to the current selected items
        """
        data = []
        selected = -1
        while True:
            selected = self.listCtrl.GetNextItem(selected, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED)
            if selected >= 0:
                selectedItem = self.listCtrl.GetItem(selected)
                if selectedItem and self.single:
                    return self.mapping[selectedItem.GetData()]
                else:
                    data.append(self.mapping[selectedItem.GetData()])
            else:
                break
        if self.single:
            return None
        else:
            return data

    def SetImageList(self, imageList, fileType):
        return self.listCtrl.SetImageList(imageList, fileType)

    def getWidth(self):
        width = 0
        for column in xrange(self.listCtrl.GetColumnCount()):
            width += self.listCtrl.GetColumnWidth(column)
        return width

    def getListCtrl(self):
        """
        Returns the wrapped, raw listctrl element
        """
        return self.listCtrl

    def getMode(self):
        """
        Returns the mode of the easylistctrl: file, string, etc
        """
        return self.mode

    # noinspection PyBroadException
    def setMode(self, mode):
        """
        Resets the easylistctrl into the specified mode, creates the correct column names, etc
        """
        self.count = 0
        self.mapping = {}
        self.columns = 1
        columnNames = None
        hideColumns = None
        columnFormats = None
        self.mode = mode
        self.listCtrl.ClearAll()
        if mode == self.DRIVE:
            self.columns = 4
            columnNames = ['Name', 'Free', 'Total', 'Chkdsk']
            hideColumns = False
            columnFormats = [wx.LIST_FORMAT_LEFT, wx.LIST_FORMAT_RIGHT, wx.LIST_FORMAT_RIGHT, wx.LIST_FORMAT_RIGHT]
        elif mode == self.FILE:
            self.columns = 3
            columnNames = ['Name', 'Size', 'Modified Time']
            hideColumns = False
            columnFormats = [wx.LIST_FORMAT_LEFT, wx.LIST_FORMAT_RIGHT, wx.LIST_FORMAT_RIGHT]
        elif mode == self.STRING:
            hideColumns = True
        if hideColumns:
            self.listCtrl.SetSingleStyle(wx.LC_NO_HEADER, True)
        else:
            self.listCtrl.SetSingleStyle(wx.LC_NO_HEADER, False)
        for i in xrange(self.columns):
            try:
                name = columnNames[i]
            except:
                name = ''
            try:
                colFormat = columnFormats[i]
            except:
                colFormat = wx.LIST_FORMAT_LEFT
            self.listCtrl.InsertColumn(i, name, colFormat)

    def populateDrives(self, drives):
        """
        drives -- an iterable of drive dictionaries to populate in the listctrl
        """
        driveObjs = DriveUtil.getDriveDictionary()
        drives = sorted(drives, key=lambda x: x[AEnums.NAME], reverse=True)
        content = [x for x in drives if x[AEnums.TYPE] == AEnums.CONTENT]
        backup = [x for x in drives if x[AEnums.TYPE] == AEnums.BACKUP]
        for drive in backup + content:
            d = drive
            newItem = wx.ListItem()
            free = "%.2f" % (float(d[AEnums.FREESPACE]) / float(1048576))
            total = "%.2f" % (float(d[AEnums.TOTALSPACE]) / float(1048576))
            freeStr = str(free) + 'GB'
            totalStr = str(total) + 'GB'
            extra = {
                1: freeStr,
                2: totalStr,
                3: d[AEnums.CHKDSK],
            }
            newItem.SetText(d[AEnums.NAME])
            fullPerc = float(d[AEnums.FREESPACE]) * 100 / float(d[AEnums.TOTALSPACE])
            color = wx.GREEN
            if fullPerc <= 10:
                color = '#B40404'
            elif fullPerc <= 15:
                color = 'YELLOW'
            newItem.SetBackgroundColour(color)
            if self.r.isLocked(d) and d[AEnums.TYPE] == AEnums.BACKUP:
                newItem.SetImage(self.fileUtil.getLocked())
            elif (d[AEnums.NAME] + d[AEnums.SERIAL]) in driveObjs:
                newItem.SetImage(self.fileUtil.getConnected())
            else:
                newItem.SetImage(self.fileUtil.getDisconnected())
            if not d[AEnums.TYPE] == AEnums.BACKUP:
                newItem.SetFont(wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.BOLD))
            self.InsertItem(newItem, drive, extra=extra)
            self.itemDataMap[newItem.GetData()] = (d[AEnums.NAME], float(free), float(total),
                datetime.datetime.strptime(d[AEnums.CHKDSK], '%Y-%m-%d') if d[AEnums.CHKDSK] else None)
        self.adjust()

    def populateStrings(self, strings):
        """
        strings -- an iterable of strings to populate in the listctrl
        """
        for string in strings:
            newItem = wx.ListItem()
            #newItem.SetText(unicode(string).encode('UTF-16'))
            newItem.SetText(string)
            newItem.SetTextColour(wx.RED)
            newItem.SetImage(self.fileUtil.getError())
            self.InsertItem(newItem, string)
            self.itemDataMap[newItem.GetData()] = [string]
        self.adjust()

    def getItemDataMap(self):
        return self.itemDataMap

    def populateFiles(self, files, fileRefIn=None, showDirs=False):
        """
        files -- an iterable of file dictionaries to populate in the listctrl
        showDirs -- if true dictionaries are populated in the listctrl as well, defaults to False
        fileRefIn -- if specified it acts as the "parent" object of the current displayed files
                     and creates ".." entry which can be used to navigate up a directory
        """
        for fileRef in files:
            newItem = wx.ListItem()
            if showDirs:
                try:
                    newItem.SetText(
                        unicode(fileRef[AEnums.PREFIX] + os.sep + fileRef[AEnums.PATH] + os.sep +
                                fileRef[AEnums.NAME]).encode(
                                    'utf-8'))
                except UnicodeEncodeError:
                    continue
            else:
                newItem.SetText(unicode(fileRef[AEnums.NAME]).encode('utf-8'))
            newItem.SetImage(self.fileUtil.getIcon(fileRef))
            if fileRef[AEnums.DIR] == str(True):
                newItem.SetTextColour(wx.BLUE)
                self.InsertItem(newItem, fileRef)
                self.itemDataMap[newItem.GetData()] = [newItem.GetText(), 0, datetime.datetime.min()]
            else:
                size = "%.2f" % (float(fileRef[AEnums.SIZE]) / float(1024))
                sizeStr = str(size)
                sizeStr += 'MB'
                dateStr = datetime.datetime.fromtimestamp(
                    fileRef[AEnums.MTIME]).strftime('%m-%d-%Y %H:%M:%S')
                extra = {1: sizeStr, 2: dateStr}
                self.InsertItem(newItem, data=fileRef, extra=extra)
                self.itemDataMap[newItem.GetData()] = [newItem.GetText(), float(size), fileRef[AEnums.MTIME]]


        if fileRefIn is not None:
            if AEnums.PARENT in fileRefIn:
                p = self.r.getFile(fileRefIn[AEnums.PARENT])
                if not p:
                    p = self.parent.buManager.getDrive(fileRefIn)
                newItem = wx.ListItem()
                newItem.SetText("..")
                newItem.SetImage(self.fileUtil.typeIndex['folder'])
                newItem.SetTextColour(wx.BLUE)
                self.InsertItem(newItem, p)

        self.adjust()

    def setSelectedDrive(self, fileRef):
        """
        fileRef -- a file whose drive should be highlighted / selected in the listctrl
        """
        for i in range(0, self.listCtrl.GetItemCount()):
            ref = self.mapping[self.listCtrl.GetItem(i).GetData()]
            if ref.getName() == fileRef.getVolName() and ref.GetSerial() == fileRef.getSerial():
                self.listCtrl.SetItemState(i, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED)
                self.auto = True
                return

    def setSelectedFiles(self, fileRef):
        """
        fileRef -- a file who should be highlighted / selected in the listctrl
        """
        for i in range(0, self.listCtrl.GetItemCount()):
            ref = self.mapping[self.listCtrl.GetItem(i).GetData()]
            if ref.getName() == fileRef.getName() and ref.getDir() == fileRef.getDir():
                self.auto = True
                self.listCtrl.SetItemState(1, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED)
                return

    def isAutoSelect(self):
        if self.auto:
            self.auto = False
            return True
        return False

    def DeleteAllItems(self):
        """
        Completely empties all mappings and the underlying listctrl
        """
        self.count = 0
        self.mapping = {}
        return self.listCtrl.DeleteAllItems()

    def GetNextItem(self, item, options):
        return self.listCtrl.GetNextItem(item, options)

    def InsertItem(self, item, data=None, extra=None):
        """
        item -- a listctlritem to insert into the listctrl
        data -- data that should be mapped to the provided item
        extra -- additional columns to be inserted with the item
        """
        self.mapping[self.count] = data
        item.SetData(self.count)
        wxId = item.GetId()
        self.count += 1
        result = self.listCtrl.InsertItem(item)
        if extra:
            for col in extra:
                self.listCtrl.SetStringItem(wxId, col, extra[col])
        return result

    def GetData(self, item):
        """
        item -- a listctrl item to find mapped data for
        """
        data = self.mapping[item.GetData()]
        return data

    def adjust(self):
        """
        Automatically adjusts column widths based on content
        """
        for i in xrange(self.columns):
            self.listCtrl.SetColumnWidth(i, wx.LIST_AUTOSIZE)
